#!/usr/bin/perl

######################################################################
package Swignition::Export::Entity;
######################################################################

use CGI::Util;
use Swignition::Export::Alarm;
use Swignition::Export::BlogEntry;
use Swignition::Export::Calendar;
use Swignition::Export::Contact;
use Swignition::Export::Event;
use Swignition::Export::FBInfo;
use Swignition::Export::Feed;
use Swignition::Export::Location;
use Swignition::Export::Recording;
use Swignition::Export::Todo;
use Swignition::Export;
use Swignition::MagicString;
use Swignition::RDFModel::Property;
use Swignition::RDFModel::Subject;
use Swignition::RDFModel::Value;
use Digest::SHA1 qw(sha1_hex);
use HTML::Entities qw(encode_entities_numeric);
use JSON 2.0;
use strict;

our $Encounters = {};
our $FullN3 = 0;

sub is
{
	my $subject = shift;
	return unless ($subject);
	return 1;
}

sub outputs
{
	return [
		{
			code  => 'RDF',
			name  => 'RDF/XML',
			type  => 'application/rdf+xml',
			multi => 1
		},
		{
			code  => 'Turtle',
			name  => 'RDF/Turtle',
			type  => 'application/rdf+turtle',
			multi => 1
		},
		{
			code  => 'JSON',
			name  => 'RDF/JSON',
			type  => 'application/x-rdf+json',
			multi => 1
		},
		{
			code  => 'TriX',
			name  => 'RDF/TriX',
			type  => 'text/xml',
			multi => 1
		},
		{
			code  => 'Detect',
			name  => 'Detection Page',
			type  => 'text/html',
			multi => 1
		},
		{
			code  => 'Title',
			name  => 'Entity Title',
			type  => 'text/plain'
		}
	];
}


sub outTitle
{
	my $triples = $_[0];
	my $S       = $_[1];	
	my $subject = findSubject($triples, $S);

	my $t = firstOfLiteral($subject,
		['http://purl.org/dc/terms/title',
		'http://purl.org/dc/elements/1.1/title',
		'http://www.w3.org/2000/01/rdf-schema#label',
		'http://www.w3.org/2006/vcard/ns#fn',
		'http://www.w3.org/2001/vcard-rdf/3.0#FN',
		'http://xmlns.com/foaf/0.1/fullName',
		'http://xmlns.com/foaf/0.1/name',
		'http://www.w3.org/2000/10/swap/pim/contact#knownAs',
		'http://www.w3.org/2006/vcard/ns#street-address',
		'http://www.w3.org/2001/vcard-rdf/3.0#STREET-ADDRESS',
		'http://bblfish.net/work/atom-owl/2006-06-06/#title',
		'http://purl.org/rss/1.0/title',	
		'http://www.w3.org/2002/12/cal/ical#summary',
		'http://buzzword.org.uk/rdf/icalx#xWrCalname'],
		['http://www.w3.org/1999/02/22-rdf-syntax-ns#value']);
	
	return $t if (length $t);
	return $1 if ($S =~ /^bnode\:\/\/\/(.*)$/);
	return $S;
}


sub multiDetect
{
	my $triples  = shift;
	my @subjects = keys %$triples;
	my $page;
	while (!$page && @subjects)
	{
		my $subj = shift @subjects;
		$page = $triples->{$subj}->{page};
	}
	
	my $rv;
	$rv .= "<div class=\"hRDF\" typeof=\"hrdf:Block\" id=\"hRDF-page\">\n";
	$rv .= "<h2>Page: <span class=\"fn\" property=\"dc:title\">"
		.encode_entities_numeric(outTitle($triples, $page->uri))
		."</span></h2>\n";
	$rv .= "<ul>\n";
	my $uri = $page->uri;
	$uri =~ s/^http\:\/\///;
	$uri = encode_entities_numeric(CGI::Util::escape(CGI::Util::escape($uri)));
	$uri =~ s/\%252F/\//ig;
	foreach my $k (qw(Entity+rdf-xml+RDF/XML Entity+rdf-json+RDF/JSON
		Entity+turtle+Turtle Contact+vcard+vCard Contact+jcard+jCard
		Location+kml+KML Calendar+icalendar+iCalendar Recording+m3u+M3U))
	{
		my ($kind, $path, $format) = split /\+/, $k;
		$kind = lc($kind).'s';
		$kind =~ s/ys$/ies/;
		$kind = "All $kind";
		$kind = "Page" if ($kind eq 'All entities');
		$rv .= "\t<li><a rel=\"xhv:alternate\" href=\"http://srv.buzzword.org.uk/$path/$uri\">$kind as $format</a></li>\n";
	}
	$rv .= "</ul>\n";
	$rv .= "</div>\n";
	$rv .= "<h2>Items Found on Page</h2>\n";
	foreach my $s (sort SubjectSort (keys %$triples))
	{
		$rv .= outDetect($triples, $s, 'h3', 1);
	}
	return $rv;
}


sub outDetect
{
	my $triples = shift;
	my $S       = shift;	
	my $htag    = shift || 'h2';
	my $dolink  = shift || 0;
	my $subject = findSubject($triples, $S);
	my $rv;
	my $links   = _outDetect_links($triples, $S);
	my $turtle  = outTurtle($triples, $S, 'html', $dolink);
	my $id      = 'hRDF-'.sha1_hex($S);
	return unless (length $turtle);

	$rv .= "<div class=\"hRDF\" typeof=\"hrdf:Block\" id=\"$id\">\n";
	$rv .= "<$htag class=\"fn\" property=\"dc:title\">".encode_entities_numeric(outTitle($triples, $S))."</$htag>\n";
	$rv .= "<div class=\"url\">&lt;<span class=\"value\">$S</span>&gt;</div>\n" unless $S =~ /^bnode/;
	$rv .= "<pre class=\"data\" property=\"hrdf:data\" datatype=\"hrdf:turtle\">";
	$rv .= $turtle;
	$rv .= "</pre>\n";
	$rv .= $links if (length $links);
	$rv .= "</div>\n";
	
	return $rv;
}

#	my $rv;
#	if ($S eq '-')
#		{ $rv = "\t<dt>(All on page)</dt>\n"; }
#	else
#		{ $rv = "\t<dt>".encode_entities_numeric(outTitle($triples, $S))."</dt>\n"; }

sub _outDetect_links
{
	my $triples = $_[0];
	my $S       = $_[1];
	
	my $subject = findSubject($triples, $S);
	my $uri;

	if ($subject->{page})
	{
		$uri = $subject->{page}->uri;
		if ($uri.'#' eq substr($S, 0, length($uri)+1))
		{
			$uri = $S;
		}
		else
		{
			$uri .= "#subject($S)";
		}
	}
	else
	{
		return undef;
	}
	$uri =~ s/^http\:\/\///;
	$uri = encode_entities_numeric(CGI::Util::escape(CGI::Util::escape($uri)));
	$uri =~ s/\%252F/\//ig;
	
	my $rv = "<ul>\n";
	foreach my $k (qw(Entity+rdf-xml+RDF/XML Entity+rdf-json+RDF/JSON 
		Entity+turtle+Turtle Contact+vcard+vCard Contact+jcard+jCard Location+kml+KML
		Calendar+icalendar+iCalendar Feed+atom+Atom Feed+icalendar+iCalendar
		Event+icalendar+iCalendar BlogEntry+icalendar+iCalendar
		Alarm+icalendar+iCalendar FBInfo+icalendar+iCalendar
		Todo+icalendar+iCalendar Recording+m3u+M3U Recipe+recipebook+RecipeBook))
	{
		my ($kind, $path, $format) = split /\+/, $k;
		
		if (
			($S eq '-')
		||	($kind eq 'Contact' && Swignition::Export::Contact::is($subject))
		||	($kind eq 'Entity' && Swignition::Export::Entity::is($subject))
		||	($kind eq 'Location' && Swignition::Export::Location::is($subject))
		||	($kind eq 'Calendar' && Swignition::Export::Calendar::is($subject))
		||	($kind eq 'Event' && Swignition::Export::Event::is($subject))
		||	($kind eq 'BlogEntry' && Swignition::Export::BlogEntry::is($subject))
		||	($kind eq 'Recording' && Swignition::Export::Recording::is($subject))
		||	($kind eq 'Alarm' && Swignition::Export::Alarm::is($subject))
		||	($kind eq 'FBInfo' && Swignition::Export::FBInfo::is($subject))
		||	($kind eq 'Todo' && Swignition::Export::Todo::is($subject))
		||	($kind eq 'Feed' && Swignition::Export::Feed::is($subject))
		||	($kind eq 'Recipe' && Swignition::Export::Recipe::is($subject))
		)
		{
			$rv .= "\t<li><a rel=\"xhv:alternate\" href=\"http://srv.buzzword.org.uk/$path/$uri\">$kind as $format</a></li>\n";
		}
	}
	$rv .= "</ul>\n";
	
	return $rv;
}


sub multiJSON
{
	my $triples = shift;
	my %rv;
	foreach my $s (keys %$triples)
	{
		my $snice = $s;
		$snice = "_:$1" if ($s =~ m|^bnode:/*(.*)|);
		my $json = _outJSON($triples, $s);

		$rv{$snice} = $json if $json;
		foreach my $r (keys %{$json->{'additional_resources'}})
		{
			$rv{$r} = $json->{'additional_resources'}->{$r};
		}
		delete $json->{'additional_resources'};
	}
	
	$rv{'$schema'}->{'$ref'} = 'http://soapjr.org/schemas/RDF_JSON';	
 	return to_json(\%rv, {utf8 => 1, pretty => 1, canonical => 1, convert_blessed => 1, allow_blessed => 1})."\n";
}

sub outJSON
{
	my $triples = $_[0];
	my $S       = $_[1];
	my $json    = _outJSON($triples, $S);

	my $snice   = $_[1];
	$snice      = "_:$1" if ($_[1] =~ m|^bnode:/*(.*)|);

	my %rv;
	$rv{$snice} = $json;
	foreach my $r (keys %{$json->{'additional_resources'}})
	{
		$rv{$r} = $json->{'additional_resources'}->{$r};
	}
	delete $json->{'additional_resources'};

	$rv{'$schema'}->{'$ref'} = 'http://soapjr.org/schemas/RDF_JSON';	
 	return to_json(\%rv, {utf8 => 1, pretty => 1, canonical => 1, convert_blessed => 1, allow_blessed => 1 })."\n";
}

sub multiTurtle
{
	my $rv;
	my $triples = shift;
	my $pfxs = {};
	
	foreach my $s (sort SubjectSort (keys %$triples))
	{
		next if ( Swignition::Export::Entity::Collection::is( findSubject($triples, $s) ) );
	
		my $R = outTurtle($triples, $s);
		if (length $R)
		{
			$R =~ m/^(.*?)\n\n(.*)$/s;
			$rv .= "$2\n";
			
			my @p = split /\n/, $1;
			foreach my $p (@p)
				{ $pfxs->{$p} = 1; }
		}
	}

	return join("\n", (sort keys %$pfxs))."\n\n".$rv;
}

sub _optEnt
{
	my $str = shift;
	my $fmt = shift;
	
	return encode_entities_numeric($str)
		if ($fmt eq 'html');
	return $str;
}

sub outN3
{
	$Swignition::Export::Entity::FullN3 = 1;
	my $rv = outTurtle(@_);
	$Swignition::Export::Entity::FullN3 = 0;
	return $rv;
}

sub multiN3
{
	$Swignition::Export::Entity::FullN3 = 1;
	my $rv = multiTurtle(@_);
	$Swignition::Export::Entity::FullN3 = 0;
	return $rv;
}

sub outTurtle
{
	my $triples = shift;
	my $S       = shift;
	my $format  = shift || 'text';
	my $dolink  = shift || 0;
	my $subject = findSubject($triples, $S);
	my $this    = $subject->{page};
	my $json    = _outJSON($triples, $S);
	my $nPredicates;

	return undef unless (keys %$json);

	return ''
		if ($Swignition::Export::Entity::Encounters->{ $S } == 1
		&&  $Swignition::Export::Entity::FullN3
		&&  $S =~ /^(bnode|geo)\:/i);

	$Swignition::Export::Entity::Encounters->{ $S } = 1;

	my $rv;
	my $pfxs = {};
	if ($S =~ m|^bnode:/*(.*)|)
		{ $rv = _optEnt("_:$1 ",$format); }
	else
		{ $rv = _optEnt("<$S> ",$format); }
		
	foreach my $predicate (sort PredicateSort (keys %$json))
	{
		next if ($predicate eq 'http://www.w3.org/2006/link#uri');
		my $prednice = Swignition::Namespace::to_curie($this, $predicate);
		$prednice = "<$predicate>" unless ($prednice);
		next unless ($prednice =~ /[\:\/]/);
		$prednice = 'a' if ($prednice eq 'rdf:type');
		
		$nPredicates++;
		
		$pfxs->{ _optEnt(sprintf("\@prefix %-9s <%s> .", $this->{NamespacePrefixes}->{ $1 }.':', $1), $format) } = 1
			if ($predicate =~ /^(.*[\#\/\?\=\:])([^\#\/\?\=\:]*)$/
			&&  $this->{NamespacePrefixes}->{ $1 });
		
		if ($format eq 'html')
			{ $rv .= '<b>'.encode_entities_numeric($prednice).'</b> '; }
		else
			{ $rv .= "$prednice "; }
		
		foreach my $value (@{ $json->{$predicate} })
		{
			my ($more, $valnice) = valnice($value, $this, $pfxs, $format, $dolink);
			
			# Small whitespace adjustments.
			if ($rv =~ / \,\n\t\t$/)
				{ $valnice =~ s/^\n\t\t//; }
			if ($valnice =~ /^\(.*\n/)
				{ $rv .= "\n\t\t"; }

			# Add commas.
			if ($prednice eq 'a')
				{ $rv .= $valnice . " , "; }
			else
				{ $rv .= $valnice . " ,\n\t\t"; }

			# If valnice returns some prefixes, add them to the list.
			my @more = split /\n/, $more;
			foreach my $m (@more)
				{ $pfxs->{$m} = 1 if ($m =~ /^\@prefix/); }
		}
		
		$rv =~ s/\,(\n\t\t| )$/;\n\t/;
		$rv =~ s/\] \;\n\t$/\] \;\n\t/;
	}

	$rv =~ s/\;\n\t$/.\n/;
	
	return '' unless ($nPredicates);

	return join("\n", (sort keys %$pfxs))."\n\n".$rv;
}

sub valnice
{
	my $value   = shift;
	my $this    = shift;
	my $triples = $this->{RDF};
	my $pfxs    = shift;
	my $format  = shift;
	my $dolink  = shift;
	my $valnice = '';
	
	if ($value->{type} eq 'uri' || $value->{type} eq 'bnode')
	{
		$value->{value} =~ s/^_:/bnode:\/\/\//;
	
		my $vURI;
		my $vSubject;
		$vSubject = findSubject($triples, $value->{value});
		$vURI     = '#hRDF-'.sha1_hex($vSubject->uri) if ($vSubject);

		return Swignition::Export::Entity::Collection::_outTurtle($triples, $vSubject->uri, $format, $dolink, $pfxs)
			if (Swignition::Export::Entity::Collection::is($vSubject));

		$valnice = Swignition::Namespace::to_curie($this, $value->{value});
		
		# Nesting.
		if ($Swignition::Export::Entity::FullN3
		&&  $value->{value} =~ /^(geo|bnode)\:/)
		{
			if ($Swignition::Export::Entity::Encounters->{ $value->{'value'} } != 1)
			{
				my $sub = outTurtle($triples, $value->{'value'}, $format, $dolink);
				$sub =~ m/^(.*?)\n\n(.*)$/s;
				my $names  = $1 . "\n";
				my $nested = $2;
				
				$nested =~ s/^(\S+) /= $1 \;\n\t/;
				$nested =~ s/^(\t+)/$1\t  /gm;
				$nested =~ s/\s*\.\n?$//;
				
				return ($names, "\n\t\t[ $nested ]");
			}
		}
		# BNodes
		elsif ($value->{value} =~ /^bnode\:\/*(.*)$/)
		{
			if ($dolink && length $vURI)
				{ $valnice = "<i><a href=\"$vURI\">"._optEnt("_:$1", $format)."</a></i>"; }
			else
				{ $valnice = ($format eq 'html') ? ('<i>'._optEnt("_:$1", $format).'</i>') : "_:$1"; }
		}
		# CURIEs
		elsif ($valnice =~ /.+\:.+/)
		{
			$valnice = ($format eq 'html') ? ('<i>'._optEnt($valnice, $format).'</i>') : $valnice;

			$pfxs->{ _optEnt(sprintf("\@prefix %-9s <%s> .", $this->{NamespacePrefixes}->{ $1 }.':', $1), $format) } = 1
				if ($value->{value} =~ /^(.*[\#\/\?\=\:])([^\#\/\?\=\:]*)$/ && defined $this->{NamespacePrefixes}->{$1});
		}
		# URIs
		else
		{
			$valnice = $value->{value};
			if ($dolink && length $vURI)
				{ $valnice = "&lt;<a href=\"$vURI\">"._optEnt($valnice, $format)."</a>&gt;"; }
			else
				{ $valnice = _optEnt("<$valnice>", $format); }
		}
	}
	else
	{
		$valnice = $value->{value};
		$valnice =~ s/\\/\\\\/g;
		$valnice =~ s/\"/\\\"/g;
		$valnice = "\"$valnice\"";
		$valnice = "\"\"$valnice\"\""
			if ($valnice =~ /[\r\n]/);
		$valnice .= '@'.$value->{lang}			
			if (length $value->{lang});
		$valnice = _optEnt($valnice, $format);

		if (length $value->{datatype})
		{
			my $dtnice = Swignition::Namespace::to_curie($this, $value->{datatype});
			if (length $dtnice)
			{
				$pfxs->{ _optEnt(sprintf("\@prefix %-9s <%s> .", $this->{NamespacePrefixes}->{ $1 }.':', $1), $format) } = 1
					if ($value->{datatype} =~ /^(.*[\#\/\?\=\:])([^\#\/\?\=\:]*)$/);
			}
			else
			{
				$dtnice = _optEnt(sprintf('<%s>', $value->{datatype}), $format);
			}
			
			$valnice .= '^^'.$dtnice;
		}
	}
	
	return ('', $valnice);
}

sub _outJSON
{
	my $triples = $_[0];
	my $S       = $_[1];
	my $subject = findSubject($triples, $S);
	my $rv      = { };
	my $expand_list = 0;;
	my $return_null = 1;
	
	$expand_list = 1
		if ( Swignition::Export::Entity::Collection::is($subject) 
		&&  !$subject->{'properties'}->{'http://www.w3.org/1999/02/22-rdf-syntax-ns#first'});
		
	if ($expand_list)
	{
		$rv->{'additional_resources'} = {};
		my $n = 1;
		my $old_bnode;
		
		my $pfx = ($S =~ /^bnode:...(.+)/) ? $1.'_' : '';
		
		while ($n)
		{
			my $item = Swignition::Export::Entity::Collection::get_list_item($triples, $S, $n);
			my $v = {
					'type'     => ($item->type
					                 ? (($item->toString =~ /^bnode:/) ? 'bnode' : 'uri') 
					                 : 'literal'),
					'value'    => ((($item->toString =~ /^bnode:\/\/\/(.*)$/) && $item->type) 
					                 ? ('_:'.$1)
					                 : $item->toString),
					'datatype' => $item->datatype
				};
			delete $v->{datatype} unless $v->{datatype};

			my $new_bnode;
			
			if ($item->type
			&&  $item->toString eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil')
			{
				if ($n==1)
				{
					push @{ $rv->{'http://www.w3.org/1999/02/22-rdf-syntax-ns#first'} }, $v;
				}
				elsif ($n==2)
				{
					push @{ $rv->{'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest'} }, $v;
				}
				else
				{
					push @{ $rv->{'additional_resources'}
					           ->{$old_bnode}
					           ->{'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest'} }, $v;
				}
				
				last;
			}
			elsif ($n==1)
			{
				push @{ $rv->{'http://www.w3.org/1999/02/22-rdf-syntax-ns#first'} }, $v;
			}
			elsif ($n==2)
			{
				$new_bnode = '_:'.Swignition::GenericParser::Utils::BNode(undef, $pfx.'Sublist');
				push @{ $rv->{'additional_resources'}
				           ->{$new_bnode}
				           ->{'http://www.w3.org/1999/02/22-rdf-syntax-ns#first'} }, $v;
				push @{ $rv->{'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest'} },
					{
						'type'  => 'bnode',
						'value' => $new_bnode
					};
				push @{ $rv->{'additional_resources'}
				           ->{$new_bnode}
				           ->{'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'} },
					{
						'type'  => 'uri',
						'value' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#List'
					};
			}
			else
			{
				$new_bnode = '_:'.Swignition::GenericParser::Utils::BNode(undef, $pfx.'Sublist');
				push @{ $rv->{'additional_resources'}
				           ->{$new_bnode}
				           ->{'http://www.w3.org/1999/02/22-rdf-syntax-ns#first'} }, $v;
				push @{ $rv->{'additional_resources'}
				           ->{$old_bnode}
				           ->{'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest'} },
					{
						'type'  => 'bnode',
						'value' => $new_bnode
					};
				push @{ $rv->{'additional_resources'}
				           ->{$new_bnode}
				           ->{'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'} },
					{
						'type'  => 'uri',
						'value' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#List'
					};
			}
			
			$old_bnode = $new_bnode;
			$n++;
		}
	}

	foreach my $prop (values %{$subject->{properties}})
	{
		next if ($expand_list
		     && ($prop->uri =~ m|^http://buzzword.org.uk/rdf/list-items/_|));

		$return_null = 0;
		
		my @values;
		foreach my $val (@{$prop->valObjects})
		{
			next if ($prop->uri eq 'http://www.w3.org/2006/link#uri'
				&& $val->toString =~ /^bnode:/);

			my $V = {};
			$V->{type}  = $val->type ? (($val->toString =~ /^bnode:/) ? 'bnode' : 'uri') : 'literal';
			$V->{value} = $val->toString;
			if (isms($val->toString))
			{
				$V->{lang}  = $val->toString->{lang};
				$V->{value} = $val->toString->{string};
			}
			elsif ((ref $val->toString) =~ /^DateTime/i)
			{
				$V->{value} = "".$val->toString;
			}
			if ( $val->datatype )     { $V->{datatype} = $val->datatype; }
			
			if ($val->type && $val->toString =~ m|^bnode:/*(.*)|)
				{ $V->{value} = "_:$1"; }
			
			push @values, $V;
		}
		
		$rv->{ $prop->uri } = \@values if (@values);
	}

	return undef if ($return_null);
	return $rv;
}


sub _outTriX
{
	my $triples = $_[0];
	my $S       = $_[1];

	my $json    = from_json(outJSON($triples, $S), {utf8 => 1});
	delete $json->{'$schema'};

	my $rv      = '';
	
	#############################################################################
	## TODO:                                                                   ##
	## Don't generate this directly from triple store: run through multiJSON.  ##
	## I can't remember why this is a TODO :-| Seems OK as-is to me.           ##
	#############################################################################
	
	foreach my $subject (sort SubjectSort (keys %$json))
	{
		foreach my $property ( sort PredicateSort (keys %{$json->{$subject}}) )
		{
			foreach my $val (@{ $json->{$subject}->{$property} })
			{
				$rv .= "\t\t<triple>\n";
				
				if ($subject =~ /^_:.+$/)
				{
					$rv .= sprintf("\t\t\t<id>%s</id>\n",
						encode_entities_numeric($subject));
				}
				else
				{
					$rv .= sprintf("\t\t\t<uri>%s</uri>\n",
						encode_entities_numeric($subject));
				}
	
				$rv .= sprintf("\t\t\t<uri>%s</uri>\n",
					encode_entities_numeric($property));
				
				if ($val->{type} eq 'bnode')
				{
					$rv .= sprintf("\t\t\t<id>%s</id>\n",
						encode_entities_numeric($val->{value}));
				}
				elsif ($val->{type} eq 'uri')
				{
					$rv .= sprintf("\t\t\t<uri>%s</uri>\n",
						encode_entities_numeric($val->{value}));
				}
				elsif ($val->{datatype})
				{
					$rv .= sprintf("\t\t\t<typedLiteral datatype=\"%s\">%s</typedLiteral>\n",
						encode_entities_numeric($val->{datatype}),
						encode_entities_numeric($val->{value}));
				}
				elsif ($val->{lang})
				{
					$rv .= sprintf("\t\t\t<plainLiteral xml:lang=\"%s\">%s</plainLiteral>\n",
						$val->{lang},
						encode_entities_numeric($val->{value}));
				}
				else
				{
					$rv .= sprintf("\t\t\t<plainLiteral>%s</plainLiteral>\n",
						encode_entities_numeric($val->{value}));
				}
				$rv .= "\t\t</triple>\n";
			}		
		}
	}
	
	return $rv;
}


sub outTriX
{
	my $triples = $_[0];
	my $S       = $_[1];
	
	return "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
		. "<!DOCTYPE graphset SYSTEM \"http://www.w3.org/2004/03/trix/trix-1/trix-1.0.dtd\">\n"
		. "<graphset\n"
		. "\txmlns=\"http://www.w3.org/2004/03/trix/trix-1/\"\n"
		. "\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
		. "\txsi:schemaLocation=\"http://www.w3.org/2004/03/trix/trix-1/\n"
		. "\t\thttp://www.w3.org/2004/03/trix/trix-1/trix-1.0.xsd\">\n"
		. "\t<graph>\n"
		. _outTriX($triples, $S)
		. "\t</graph>\n"
		. "</graphset>\n";
}


sub multiTriX
{
	my $triples = $_[0];
	my @keys = keys %$triples;
	
	my $rv = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
		. "<!DOCTYPE graphset SYSTEM \"http://www.w3.org/2004/03/trix/trix-1/trix-1.0.dtd\">\n"
		. "<graphset\n"
		. "\txmlns=\"http://www.w3.org/2004/03/trix/trix-1/\"\n"
		. "\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
		. "\txsi:schemaLocation=\"http://www.w3.org/2004/03/trix/trix-1/\n"
		. "\t\thttp://www.w3.org/2004/03/trix/trix-1/trix-1.0.xsd\">\n"
		. "\t<graph>\n";
	foreach my $S (sort SubjectSort @keys)
	{
		$rv .= _outTriX($triples, $S);
	}
	$rv .= "\t</graph>\n"
		. "</graphset>\n";
	
	return $rv;
}


sub _outRDF
{
	my $triples = $_[0];
	my $S       = $_[1];
	my $zeal    = $_[2];
	my $subject = findSubject($triples, $S);
	my $this    = $subject->{page};
	my $rv = '';

	return unless ($subject && (ref $subject eq 'Swignition::RDFModel::Subject'));
	
	$Swignition::Export::Entity::Encounters{$S} = 1;

	my $tag = 'rdf:Description';
	my $rdfHeadType = firstOf($subject, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
	if ($rdfHeadType)
		{ $tag = Swignition::Namespace::to_curie($this, $rdfHeadType, 1); }

	$rv .= sprintf("\t<$tag%s>\n", rdf_about($triples, $subject->uri, 1));
	foreach my $p (sort PredicateSort keys %{ $subject->{properties} })
	{
		my $property = $subject->property($p);
		next if ($property->nVals == 0);
		next unless (length $property->namespace);
		my $pfxprop = Swignition::Namespace::to_curie($this, $property->uri, 1);

		# Weird bug fix.
		if ($property->uri eq 'http://www.w3.org/2006/link#uri'
		&& !$this->{'NamespacePrefixes'}->{'http://www.w3.org/2006/link#'}
		&& $pfxprop eq 'p:uri'
		&& !$this->{'NamespacePrefixesReverse'}->{'link'})
		{
			$this->{'NamespacePrefixesReverse'}->{'link'} = 'http://www.w3.org/2006/link#uri';
			$this->{'NamespacePrefixes'}->{'http://www.w3.org/2006/link#uri'} = 'link';
			
			$pfxprop = 'link:uri';
		}
	
		foreach my $val (@{ $property->valObjects })
		{
			# These are just waffle, so skip them.
			next if ($property->uri eq 'http://www.w3.org/2006/link#uri'
				&& $val->toString =~ /^bnode:/);
			next if ($property->uri eq 'http://www.w3.org/2006/link#uri'
				&& $val->toString eq $S);
			next if ($property->uri eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'
				&& $val->toString eq $rdfHeadType);
				
			my $langattr = '';
			if (isms($val->toString) && $val->toString->{lang})
				{ $langattr = sprintf(" xml:lang=\"%s\"", encode_entities_numeric($val->toString->{lang})); }

			my $datatypeattr = '';
			if ( $val->datatype )
				{ $datatypeattr = sprintf(" rdf:datatype=\"%s\"", encode_entities_numeric($val->datatype)); }
			
			my $xmlnsattr = '';
			my $valstr;
			if ($val->datatype eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral' && !$val->type)
			{
				$valstr = $val->toString;
				$xmlnsattr = sprintf(" rdf:parseType=\"Literal\" xmlns=\"%s\"", encode_entities_numeric($val->{xmlns})); 
				$xmlnsattr = ' rdf:parseType="Literal"'
					unless (length $val->{xmlns});
			}
			else
			{
				$valstr = encode_entities_numeric($val->toString); 
			}

			if ($val->type == 1)
			{
				if (defined $triples->{$valstr}
				&& ($valstr =~ m#^(bnode:|tag:|http...openurl.invalid)# || $zeal)
				&& !$Swignition::Export::Entity::Encounters{$valstr})
				{
					if ( Swignition::Export::Entity::Collection::is($triples->{$valstr}) )
					{
						my $nested = Swignition::Export::Entity::Collection::_outRDF(
							$triples, $valstr, $zeal);
						$nested =~ s/^/\t\t/gm;
						$rv .= sprintf("\t\t<%s rdf:parseType=\"Collection\">\n%s\t\t</%s>\n", $pfxprop, $nested, $pfxprop);
					}
					else
					{
						my $nested = _outRDF($triples, $valstr, $zeal);
						$nested =~ s/^/\t\t/gm;
						$rv .= sprintf("\t\t<%s>\n%s\t\t</%s>\n", $pfxprop, $nested, $pfxprop);
					}
				}
				else
				{
					$rv .= sprintf("\t\t<%s%s />\n", $pfxprop, rdf_resource($triples, $valstr));
				}
			}
			elsif (length $val->vocab)
			{
				my $pfxvocab = Swignition::Namespace::to_curie($this, $val->vocab, 1);
			
				$rv .= sprintf("\t\t<%s>\n\t\t\t<%s>\n\t\t\t\t<rdf:value%s%s%s>%s</rdf:value>\n\t\t\t</%s>\n\t\t</%s>\n", 
									$pfxprop, $pfxvocab, $langattr, $datatypeattr, $xmlnsattr, $valstr, $pfxvocab, $pfxprop);
			}
			else
			{
				$rv .= sprintf("\t\t<%s%s%s%s>%s</%s>\n", 
									$pfxprop, $langattr, $datatypeattr, $xmlnsattr, $valstr, $pfxprop);
			}
		}
	}
	$rv .= "\t</$tag>\n";
	
	foreach my $alias (@{$subject->
		property('http://www.w3.org/2006/link#uri')
		->valObjects})
	{
		next unless ($alias->type);
		my $A = $alias->toString;
		$rv .= sprintf("\n\t<rdf:Description%s>"
			           ."\n\t\t<%s%s />"
		              ."\n\t</rdf:Description>"
			           ."\n",
			           rdf_about($triples, $A, 1),
			           Swignition::Namespace::to_curie($this,
			               'http://www.w3.org/2002/07/owl#sameAs', 1),
			           rdf_resource($triples, $subject->uri, 1))
			unless ($A eq $S || $A =~ /\#xpointer\(/ || $A =~ /^bnode:/);
	}
	
	return $rv;
}


sub outRDF
{
	my $triples = $_[0];
	my $S       = $_[1];
	my $subject = $triples->{$S};
	my $this    = $subject->{page};

	$Swignition::Export::Entity::Encounters = {};

	my $rv = "<?xml version=\"1.0\"?>\n"
		. "<rdf:RDF";

	my (%used_namespaces);
	foreach my $p ( $subject->propertiesObjects )
	{
		$used_namespaces{$p->namespace} = 1;
	}
	
	foreach my $ns (keys %{ $this->{NS} })
	{
		my $nsurl = $this->{NS}->{$ns}->{nsurl};
		next unless (length $nsurl);
		$rv .= "\n\txmlns:".lc($ns)."=\"".encode_entities_numeric($nsurl)."\""
			if ($used_namespaces{$nsurl});
	}
	$rv .= "NS_PLACEHOLDER_99754106633f94d350db34d548d6091a>\n\n";
	$rv .= _outRDF($triples, $S, 1);
	$rv .= "</rdf:RDF>\n";
	return add_namespaces($rv, $triples, $S);
}

sub multiRDF
{
	my $triples = $_[0];
	my @keys = keys %$triples;
	my $key = shift @keys;
	my $this = $triples->{$key}->{page};
	
	$Swignition::Export::Entity::Encounters = {};

	my $rv = "<?xml version=\"1.0\"?>\n"
		. "<rdf:RDF";

	foreach my $ns (keys %{ $this->{NS} })
	{
		my $nsurl = $this->{NS}->{$ns}->{nsurl};
		next unless (length $nsurl);
		$rv .= "\n\txmlns:".lc($ns)."=\"".encode_entities_numeric($nsurl)."\"";
	}
	$rv .= "NS_PLACEHOLDER_99754106633f94d350db34d548d6091a>\n\n";
	
	my @subjects = keys %$triples;
	my @sortedSubjects = sort SubjectSort @subjects;
	foreach my $S (@sortedSubjects)
	{
		unless ($Swignition::Export::Entity::Encounters{$S})
		{
			my $x = _outRDF($triples, $S);
			$rv .= "$x\n" if (length $x);
		}
	}
	
	$rv .= "</rdf:RDF>\n";
	return add_namespaces($rv, $triples);
}

sub add_namespaces 
{
	my $string  = shift;
	my $triples = shift;
	my $subject = shift;
	my $rv = '';
	my %already;
	my $page;
	
	if ($subject)
	{
		my $S = findSubject($triples, $subject);
		$page = $S->{page};
		
		foreach my $P ( $S->propertiesObjects )
		{
			my $ns = $P->namespace;
			next unless (length $ns);
			$rv .= sprintf("\n xmlns:%s=\"%s\"", $page->{'NamespacePrefixes'}->{$ns}, $ns)
				unless ($already{$page->{'NamespacePrefixes'}->{$ns}});
			$already{$page->{'NamespacePrefixes'}->{$ns}} = $ns;

			# TODO
			# Also need to check within values for vocabs as these will be
			# namespaced. Plus add OWL.
		}
	}
	else
	{
		foreach my $subject (keys %$triples)
		{
			my $S = $triples->{$subject};
			$page = $S->{page};

			next unless ($S && (ref $S eq 'Swignition::RDFModel::Subject'));
			
			foreach my $P ($S->propertiesObjects)
			{
				my $ns = $P->namespace;
				next unless (length $ns);
				$rv .= sprintf("\n xmlns:%s=\"%s\"", $page->{'NamespacePrefixes'}->{$ns}, $ns)
					unless ($already{$page->{'NamespacePrefixes'}->{$ns}});
				$already{$page->{'NamespacePrefixes'}->{$ns}} = $ns;

				if ($P->uri eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type')
				{
					foreach my $v (@{$P->valObjects})
					{					
						if ($v->type && $v->toString =~ /^(.*[\/\#\?\=\:])([^\/\#\?\=\:]+)/)
						{
							$rv .= sprintf("\n xmlns:%s=\"%s\"", $page->{'NamespacePrefixes'}->{$1}, $1)
								unless ($already{$page->{'NamespacePrefixes'}->{$1}});
							$already{$page->{'NamespacePrefixes'}->{$1}} = $1;
						}
					}
				}
			}
		}
	}
	
	# rdf itself is *always* needed because of syntactic thingies
	$rv .= "\n xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\""
		unless ($rv =~ /1999.02.22\-rdf\-syntax\-ns/);
		
	# Weird fixes.
	my $li = Swignition::Namespace::to_curie($page, 'http://buzzword.org.uk/rdf/list-items/X', 1);
	$li =~ s/.X$//;
	$rv =~ s/xmlns:=\"http:\/\/buzzword\.org\.uk\/rdf\/list-items\/\"
	        /xmlns:$li=\"http:\/\/buzzword\.org\.uk\/rdf\/list-items\/\"/x;
	$rv =~ s/\n xmlns:=\"[^\"]+\"//g;

	$string =~ s/NS_PLACEHOLDER_99754106633f94d350db34d548d6091a/$rv/;
	
	return $string;
}

sub rdf_resource
{
	my $triples = shift;
	my $uri     = shift;
	my $sure    = shift;
	
	unless (1 || $sure)
	{
		my $subject = findSubject($triples, $uri);
		return '' unless ($subject && (ref $subject eq 'Swignition::RDFModel::Subject'));
		$uri = $subject->uri if ($subject);
	}
	
	if ($uri =~ m|^bnode:///(.*)$|)
		{ return sprintf(' rdf:nodeID="%s"', encode_entities_numeric($1)); }
		
	return sprintf(' rdf:resource="%s"', encode_entities_numeric($uri));
}

sub rdf_about
{
	my $triples = shift;
	my $uri     = shift;
	my $sure    = shift;
	
	unless ($sure)
	{
		my $subject = findSubject($triples, $uri);
		$uri = $subject->uri if ($subject);
	}
	
	if ($uri =~ m|^bnode:///(.*)$|)
		{ return sprintf(' rdf:nodeID="%s"', encode_entities_numeric($1)); }
		
	return sprintf(' rdf:about="%s"', encode_entities_numeric($uri));
}

sub SubjectSort
{
	return  1 if ($a =~ /^bnode/ && $b !~ /^bnode/);
	return -1 if ($b =~ /^bnode/ && $a !~ /^bnode/);
	
	return  1 if ($a =~ /\#/ && $b !~ /\#/);
	return -1 if ($b =~ /\#/ && $a !~ /\#/);
	
	$a cmp $b;
}

sub PredicateSort
{
	my $rdftype = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';

	return -1 if ($a eq $rdftype && $b ne $rdftype);
	return  1 if ($a ne $rdftype && $b eq $rdftype);
	
	# For rdf:Lists.
	if ($a =~ /^(http...buzzword.org.uk.rdf.list-items._|http...www.w3.org.1999.02.22-rdf-syntax-ns._)(\d+)$/)
	{
		my $numA = $2;
		if ($b =~ /^(http...buzzword.org.uk.rdf.list-items._|http...www.w3.org.1999.02.22-rdf-syntax-ns._)(\d+)$/)
		{
			my $numB = $2;
			return $numA <=> $numB;
		}
	}
	
	$a cmp $b;
}

1;


######################################################################
package Swignition::Export::Entity::Collection;
######################################################################

use Swignition::Export;
use Swignition::MagicString;
use HTML::Entities qw(encode_entities_numeric);
use strict;

sub is
{
	my $subject = shift;
	my $allow_seq = shift || 0;
	return unless ($subject);
	return unless (ref $subject eq 'Swignition::RDFModel::Subject');	

	if (defined $subject->property('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'))
	{
		foreach my $Type ( @{$subject
			->property('http://www.w3.org/1999/02/22-rdf-syntax-ns#type')
			->valObjects} )
		{
			my $type = $Type->toString;
			return 1
				if ($type eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#List');
			return $allow_seq
				if ($type eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#Seq');
		}
	}

	return 1
		if (defined $subject->property('http://www.w3.org/1999/02/22-rdf-syntax-ns#first'));
	return $allow_seq
		if (defined $subject->property('http://www.w3.org/1999/02/22-rdf-syntax-ns#_1'));

	return;
}

sub _outTurtle
{
	my $triples = shift;
	my $S       = shift;
	my $format  = shift || 'text';
	my $dolink  = shift || 0;
	my $pfxs    = shift;
	my $subject = findSubject($triples, $S);

	my $rv = '( ';
	my $n = 1;
	my $more;
	while ($n)
	{
		my $item = get_list_item($triples, $S, $n);
		
		last if ($item->type 
			&& ($item->toString eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'));
			
		my ($x, $y) = Swignition::Export::Entity::valnice({
				'value'    => $item->toString ,
				'datatype' => $item->datatype ,
				'lang'     => (isms($item->toString) ? $item->toString->{lang} : undef) ,
				'type'     => ($item->type ? 'uri' : 'literal')
			}, $subject->{page}, $pfxs, $format, $dolink);

		if (($rv eq '( ') && ($y =~ /^\n\t\t\[/))
		{
			$y =~ s/^\n\t\t//;
		}

		$more .= "$x\n";
		$rv .= "$y ";
		$n++;
	}
	
	$rv .= ')';
	return ($more, $rv);
}

sub _outRDF
{
	my ($triples, $subjuri, $zeal) = (shift, shift, shift);
	my $subject = findSubject($triples, $subjuri);
	my $rv;

	if ($subjuri =~ /^bnode:\/\/\/(.*)$/)
		{ $rv = "\t<!-- rdf:List rdf:nodeID=\"".$1."\" -->\n"; }
	else
		{ $rv = "\t<!-- rdf:List rdf:about=\"".$subjuri."\" -->\n"; }
	
	my $n = 1;
	while ($n)
	{
		my $item = get_list_item($triples, $subjuri, $n, 1);
		
		last if ($item->type 
			&& ($item->toString eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'));
		
		if (!$item->type)
		{
			if ($item->datatype)
			{
				$rv .= sprintf("\t<%s>\n\t\t<rdf:value rdf:datatype=\"%s\"%s>%s</rdf:value>\n\t</%s>\n",
					Swignition::Namespace::to_curie($subject->{page}, 'http://www.w3.org/2000/01/rdf-schema#Literal', 1),
					$item->datatype,
					(isms($item->toString) ? sprintf(' xml:lang="%s"', $item->toString->{lang}) : ''),
					encode_entities_numeric($item->toString),
					Swignition::Namespace::to_curie($subject->{page}, 'http://www.w3.org/2000/01/rdf-schema#Literal', 1));
			}
			else
			{
				$rv .= sprintf("\t<%s rdf:value=\"%s\"%s />\n",
					Swignition::Namespace::to_curie($subject->{page}, 'http://www.w3.org/2000/01/rdf-schema#Literal', 1),
					encode_entities_numeric($item->toString),
					(isms($item->toString) ? sprintf(' xml:lang="%s"', $item->toString->{lang}) : ''));
			}
		}
		elsif ($Swignition::Export::Entity::Encounters{ $item->toString })
		{
			my $v = $item->toString;
			my $V = findSubject($triples, $v);
			my $type = 'rdf:Description';
			
			foreach my $t ( @{$V
				->property('http://www.w3.org/1999/02/22-rdf-syntax-ns#type')
				->valObjects} )
			{
				if ($t->type)
				{
					$type = Swignition::Namespace::to_curie($subject->{page}, $t->toString, 1);
					last;
				}
			}
		
			if ($v =~ /^bnode:\/\/\/(.*)$/)
				{ $rv .= sprintf("\t<%s rdf:nodeID=\"%s\" />\n", $type, encode_entities_numeric($1)); }
			else
				{ $rv .= sprintf("\t<%s rdf:about=\"%s\" />\n", $type, encode_entities_numeric($v)); }
		}
		else
		{
			$rv .= Swignition::Export::Entity::_outRDF($triples, $item->toString, $zeal);
		}
		
		$n++;
	}
	
	$rv .= "\t<!-- /rdf:List -->\n";

	$Swignition::Export::Entity::Encounters{$subjuri} = 1;
	return $rv;
}

sub get_list_item
{
	my $triples = shift;
	my $subjURI = shift;
	my $subject = findSubject($triples, $subjURI);
	my $num     = shift;

	if (($num==1) && defined $subject->property('http://www.w3.org/1999/02/22-rdf-syntax-ns#first'))
	{
		$Swignition::Export::Entity::Encounters{$subjURI} = 1;

		foreach my $v ( @{$subject
			->property('http://www.w3.org/1999/02/22-rdf-syntax-ns#first')
			->valObjects} )
		{
			return $v;
		}
	}
	elsif (($num>1) && defined $subject->property('http://www.w3.org/1999/02/22-rdf-syntax-ns#rest'))
	{
		my $rest = pop @{$subject
			->property('http://www.w3.org/1999/02/22-rdf-syntax-ns#rest')
			->valObjects};
		
		return get_list_item($triples, $rest->toString, $num - 1)
			if (    is( findSubject($triples, $rest->toString) )    );
	}
	elsif (($num>=1) && defined $subject->property('http://buzzword.org.uk/rdf/list-items/_'.$num))
	{
		foreach my $v ( @{$subject
			->property('http://buzzword.org.uk/rdf/list-items/_'.$num)
			->valObjects} )
		{
			return $v;
		}
	}
	elsif (($num>=1) && defined $subject->property('http://www.w3.org/1999/02/22-rdf-syntax-ns#_'.$num))
	{
		foreach my $v ( @{$subject
			->property('http://www.w3.org/1999/02/22-rdf-syntax-ns#_'.$num)
			->valObjects} )
		{
			return $v;
		}
	}
	
	return Swignition::RDFModel::Value->new('http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 1);	
}


1;
